/**
 * Created by Weizehua on 2017/1/17.
 */
import {HostInfo} from "../../https_server";
import {Metadata} from "../../Utils/Metadata/Metadata";
import {Caller} from "./Caller";
import {Singleton, Constructor} from "ts.di";
import {
    RequestUserInfo,
    RequestParam,
    HandlerType,
    formalHandlerReturn,
    HandlerParamTypeOption,
    FormalHandlerResponse,
    isHandlerSuccess,
    HandlerResponse,
    HandlerValidatorsOption,
    combineErrorMessage,
    ensureErrorMessage,
    SignParamType,
    SignParamTypeWithoutRootArray
} from "./Types";
import {tr} from "../Localization/Translator";
import {Hash} from "../Hash/Hash";
import {logger} from "../Logger/Logger";
import {keysCopy, keysExist, access} from "../../Utils/ObjectOperate/ObjectOperate";

@Singleton()
export class Dispatcher {
    constructor(public hasher: Hash) {
    }

    async handle(path: string, hostInfo: HostInfo, bodyObj: any): Promise<FormalHandlerResponse> {
        let caller: Caller;
        let resolveAndCleanup = async(res) => {
            if (caller) {
                caller.unRegisterAll();
            }
            let formalRes = await formalHandlerReturn(res, caller && caller.lastCall);

            if (!formalRes.success) {
                console.error(new Error('handler failed : ' + formalRes.reason));
            }
            if (formalRes.success && handler[Dispatcher.signSymbol]) {
                for (let {root, keys}  of handler[Dispatcher.signSymbol]) {
                    let rootObj = access(formalRes, root) as SignParamType;
                    if (rootObj instanceof Array) {
                        if (keys instanceof Array)
                            keys = keys[0];
                        for (let obj of rootObj as SignParamTypeWithoutRootArray[]) {
                            obj['signature'] = await this.sign(obj, keys);
                        }
                    }
                    else {
                        rootObj['signature'] = await this.sign(rootObj as SignParamTypeWithoutRootArray, keys);
                    }
                }
            }

            logger.debug('response: ', JSON.stringify(formalRes, null, 2));
            return formalRes;
        };

        // programming logic check
        if (!Dispatcher.handles[path]) {
            throw Error(`服务器不存在对请求${path}的处理函数。`);
        }
        let handler = Dispatcher.handles[path];

        let params = bodyObj.params;
        logger.debug('unfiltered param : ', JSON.stringify(params, null, 2));
        // @ParamType()
        if (handler[Dispatcher.paramTypeOptionsSymbol]) {
            let paramsOptions: {[idx: string]: HandlerParamTypeOption} = handler[Dispatcher.paramTypeOptionsSymbol];
            // type correcting
            params = await Dispatcher.correctType(params, paramsOptions);

            // type matched?
            let typeCheckRes = Dispatcher.typeCheck(params, paramsOptions);
            if (!await isHandlerSuccess(typeCheckRes)) {
                return resolveAndCleanup(combineErrorMessage(tr`参数类型错误。`, typeCheckRes));
            }
        }

        // @Validator()
        if (handler[Dispatcher.validatorsOptionsSymbol]) {
            let res = await formalHandlerReturn(await Dispatcher.validate(params, handler[Dispatcher.validatorsOptionsSymbol]));
            if (!res.success) {
                res.reason = res.reason || tr("参数值不合规范。");
                return resolveAndCleanup(res);
            }
        }
        logger.debug('filtered param : ', JSON.stringify(params, null, 2));

        // check for @Require()
        caller = new Caller();
        caller.register(HostInfo, hostInfo);
        caller.register(RequestUserInfo, bodyObj.userInfo);
        caller.register(Object, params);
        caller.register(RequestParam, params);
        if (handler[Dispatcher.requiresSymbol]) {
            for (let func of handler[Dispatcher.requiresSymbol]) {
                let res = await await caller.call(func);
                if (!await isHandlerSuccess(res)) {
                    return resolveAndCleanup(res);
                }
            }
        }

        // Verification
        if (handler[Dispatcher.verifySymbol]) {
            for (let {root: rootPath, keys: requiredKeys} of handler[Dispatcher.verifySymbol]) {
                let rootObj = access(params, rootPath);
                if (rootObj instanceof Array) {
                    if (requiredKeys instanceof Array)
                        requiredKeys = requiredKeys[0];
                    for (let obj of rootObj) {
                        let res = await this.verify(rootPath, obj, requiredKeys);
                        if (!await isHandlerSuccess(res)) {
                            return resolveAndCleanup(res);
                        }
                    }
                }
                else {
                    let res = await this.verify(rootPath, rootObj, requiredKeys);
                    if (!await isHandlerSuccess(res)) {
                        return resolveAndCleanup(res);
                    }
                }
            }
        }

        // Finally, call handler
        return resolveAndCleanup(await caller.call(handler));

    }

    private static async correctType(param: any, type: HandlerParamTypeOption) {
        if (type === undefined)
            return param;
        if (type === Object || type === String || type === Number || type === Boolean) {
            return type(param);
        }
        if (type.constructor === Object) {
            let obj = {};
            for (let key of Object.keys(param)) {
                obj[key] = await Dispatcher.correctType(param[key], type[key]);
            }
            return obj;
        }
        if (type instanceof Array) {
            let innerType = type[0];
            let arr = [];
            for (let key of Object.keys(param)) {
                arr.push(await Dispatcher.correctType(param[key], innerType));
            }
            return arr;
        }
        if (type === Date)
            return new type(param);
    }

    private static async typeCheck(param: any, type: HandlerParamTypeOption): Promise<HandlerResponse> {
        if (param === undefined)
            return tr`必要参数不存在`;
        if (type.constructor === Object) {
            // Specific object type, check deeper
            if (typeof param !== 'object')
                return tr`该类型应该为object，而不是${JSON.stringify(param)}`;

            for (let key of Object.keys(type)) {
                let res = await Dispatcher.typeCheck(param[key], type[key]);
                if (!await isHandlerSuccess(res)) {
                    return combineErrorMessage(`.${key}`, res, undefined, '');
                }
            }
            return true;
        }
        if (type === Object) {
            // Any object type
            return typeof param === 'object' ? true : tr("应该为object");
        }
        if (type instanceof Array) {
            // An array must be array
            if (!(param instanceof Array))
                return tr`应该为数组，而不是${typeof param}-${JSON.stringify(param)}`;

            let innerType = type[0];
            for (let key of Object.keys(param)) {
                let val = param[key];
                let res = await Dispatcher.typeCheck(val, innerType);
                if (!await isHandlerSuccess(res))
                    return combineErrorMessage(`.${key}`, res, undefined, '');
            }
            return true;
        }
        // String | Number | Boolean | Date
        return param.constructor === type ? true : tr`应该为${type}，而不是${JSON.stringify(param)}`;
    }

    private static async validate(params: any, validatorOptions: any) {
        if (validatorOptions instanceof Function) {
            let res = await validatorOptions(params);
            if (!await isHandlerSuccess(res)) {
                return ensureErrorMessage(res, '类型值不合规范。');
            }
            return true;
        }
        if (validatorOptions instanceof Array) {
            if (validatorOptions[0] instanceof Function) {
                // [validator1, validator2, validator3, ...]
                for (let validator of validatorOptions) {
                    let res = await Dispatcher.validate(params, validator);
                    if (!await isHandlerSuccess(res, validator))
                        return res;
                }
                return true;
            }
            else {
                // specific structure inside []
                // eg: [{val1: validator1, val2: validator4val2}]
                let structure = validatorOptions[0];
                for (let key of Object.keys(params)) {
                    let res = await Dispatcher.validate(params[key], structure);
                    if (!await isHandlerSuccess(res, key)) {
                        return combineErrorMessage(`.${key}`, res);
                    }
                }
                return true;
            }
        }
        if (validatorOptions.constructor === Object) {
            // specific structure
            for (let key of Object.keys(validatorOptions)) {
                let res = await Dispatcher.validate(params[key], validatorOptions[key]);
                if (!await isHandlerSuccess(res)) {
                    return combineErrorMessage(`.${key}`, res);
                }
            }
            return true;
        }
    }

    private static signingObject2Key(filteredObject: any) {
        if (!filteredObject)
            return filteredObject;
        if (filteredObject.constructor === Object) {
            let res = {};
            for (let key of Object.keys(filteredObject)) {
                res[key] = Dispatcher.signingObject2Key(filteredObject[key]);
            }
            return res;
        }
        else if (filteredObject === Array) {
            return [Dispatcher.signingObject2Key(filteredObject[0])];
        }
        else
            return true;
    }

    private async sign(obj: SignParamTypeWithoutRootArray, keys: SignParamTypeWithoutRootArray) {
        let filteredObject = keysCopy(obj, keys);
        let normalizedKeys = Dispatcher.signingObject2Key(filteredObject);
        let signature = await this.signature(filteredObject);
        return {signature: signature, signatureKeys: normalizedKeys};
    }

    private async signature(obj: {[idx: string]: any}) {
        let data = JSON.stringify(obj, null, 0);
        return this.hasher.sign(data);
    }

    async verify(rootPath: string, rootObj: any, requiredKeys: SignParamTypeWithoutRootArray) {
        if (!rootObj.signature || !rootObj.signature.signatureKeys || !rootObj.signature.signature)
            return tr`参数${rootPath}需要签名，但是没有签名！`;
        let originalSignatureKeys = rootObj.signature.signatureKeys;
        let filteredObject = keysCopy(rootObj, originalSignatureKeys);
        if (!keysExist(filteredObject, requiredKeys))
            return tr`参数${rootPath}签名不完整！`;

        let signature = await this.signature(filteredObject);
        if (rootObj.signature.signature !== signature)
            return tr`参数${rootPath}签名校验失败！`;
        return true;
    };

    static handleDecoratorFactor(path: string) {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            if (Dispatcher.handles[path]) {
                throw new Error("you have register handler for '" + path + "' twice!");
            }
            Metadata.registerTypesForClassFunc(target, propertyKey, target.constructor === Function ? target : <any>target.constructor);
            Dispatcher.handles[path] = target[propertyKey];
            logger.debug(`register handler : ${path} as ${target[propertyKey].name}`);
        }
    }

    static requireDecoratorFactor(funcOrFuncList: HandlerType | (HandlerType[])): MethodDecorator {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            Metadata.registerTypesForClassFunc(target, propertyKey, target.constructor === Function ? target : <any>target.constructor);
            let func = target[propertyKey];
            let list = func[Dispatcher.requiresSymbol] || [];
            func[Dispatcher.requiresSymbol] = list.concat(funcOrFuncList);
        }
    }

    static handlerClassDecoratorFactor(): ClassDecorator {
        return <T>(target: Constructor<T>): Constructor<T> | void => {
            logger.debug(`Handler class registered : ${target.name}`);
            Singleton()(target);
        }
    }

    static paramTypeDecoratorFactor(options: HandlerParamTypeOption): MethodDecorator {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            target[propertyKey][Dispatcher.paramTypeOptionsSymbol] = options;
        }
    }

    static validatorsDecoratorFactor(options: HandlerValidatorsOption) {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            target[propertyKey][Dispatcher.validatorsOptionsSymbol] = options;
        }
    }

    static verifyDecorator(root: string, keys: SignParamType) {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            if (!target[propertyKey][Dispatcher.verifySymbol])
                target[propertyKey][Dispatcher.verifySymbol] = [];
            target[propertyKey][Dispatcher.verifySymbol].push({root: root, keys: keys});
        }
    }

    static signDecorator(root: string, keys: SignParamType) {
        return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
            if (!target[propertyKey][Dispatcher.signSymbol])
                target[propertyKey][Dispatcher.signSymbol] = [];
            target[propertyKey][Dispatcher.signSymbol].push({root: root, keys: keys})
        }
    }

    private static paramTypeOptionsSymbol = Symbol("Dispatcher.paramTypeOptionsSymbol");
    private static validatorsOptionsSymbol = Symbol("Dispatcher.validatorsOptionsSymbol");
    private static signSymbol = Symbol("Dispatcher.signSymbol");
    private static verifySymbol = Symbol("Dispatcher.verifySymbol");
    private static requiresSymbol = Symbol("Dispatcher.requiresSymbol");
    private static handles: {[idx: string]: HandlerType} = {};

}

/* Mark an function as handler.
 * usage :
 * @Handler('/some/handler')
 * someHandler(userInfo: UserInfo, param: any) {
 * }
 *
 * The params of handler declaration can be in any style.
 * Caller will figure out what params it need and pass correct arguments to it.
 * Supported param-types are:
 * RequestUserInfo     : provide user information, including signature
 * HostInfo            : provide peer address information, such as address, port
 * RequestParam or any : params user passed
 *
 * \sa @ParamType()
 */
export let Handle = Dispatcher.handleDecoratorFactor;

/* Defines Pre-check functions for an handler.
 * If pre-check is failed, user will be notified with return value of first failed check function
 * */
//noinspection JSUnusedGlobalSymbols
export let Require = Dispatcher.requireDecoratorFactor;

//noinspection JSUnusedGlobalSymbols
export function Reverse(require: Function): () => Promise<any> {
    return async(...args) => {
        return !await isHandlerSuccess(require(...args));
    }
}

/* Mark an class as Handler's Container, registering all metadata needed for its member
 * */
export let HandlerClass = Dispatcher.handlerClassDecoratorFactor;

/* Define handler's parameter structure.
 * usage :
 * \code typescript
 * @Handler('/some/handler/')
 * @ParamType({
 *     param1: String
 *     param4: [String, Number, Date]
 *     param6: {
 *         option1 : Boolean
 *         option2 : Date
 *         }
 *     }
 * @Require([()=>isLogged(), Reverse(isPhoneRegistered)])
 * someHandler(...){...}
 * \endcode
 *
 * if parameter's structure is not matched, user will be notified.
 * */
export let ParamType = Dispatcher.paramTypeDecoratorFactor;

/*
 * \Note \a this is not usable within validators
 * */
export let Validators = Dispatcher.validatorsDecoratorFactor;

/* auto add signature to return value. can be used with @Verify(root, keys)
 * expression : @Sign(root: string, keys: Object)
 * Usage:
 * \code typescript
 * @Handle('/login')
 * @Sign(".", {id:true, metadata: true, tempUploadPath: true}) // sign all of its data.
 * @Sign("followers", [{id:true}]) // sign all elements's id filed
 * async func(...){...}
 *
 * @Handle('...')
 * @Verify(".', {tempUploadPath:true}) // verify tempUploadPath
 * @Verify("followers', {id:true}) // verify every user's id
 * async func2(...){...}
 * \endcode
 */
export let Sign = Dispatcher.signDecorator;

/* verify some data
 * \see @Sign()
 */
export let Verify = Dispatcher.verifyDecorator;

// Handler for @Require()
export function RequireHandler(): MethodDecorator {
    return <T,S>(target: Object|Constructor<S>, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
        Metadata.registerTypesForClassFunc(target, propertyKey, target.constructor === Function ? target : <any>target.constructor);
    }
}


